package com.cube.share.resubmit.check.aspect;

import com.cube.share.base.constants.Constant;
import com.cube.share.base.templates.CustomException;
import com.cube.share.base.utils.ExpressionUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;

/**
 * @author cube.li
 * @date 2021/7/9 22:17
 * @description 防重复提交切面
 */
@Component
@Aspect
@Order(-1)
@ConditionalOnProperty(name = "enabled", prefix = "resubmit-check", havingValue = "true", matchIfMissing = true)
public class RepeatSubmitCheckAspect {


    private static final String REDIS_SEPARATOR = "::";

    private static final String RESUBMIT_CHECK_KEY_PREFIX = "resubmitCheckKey" + REDIS_SEPARATOR;

    @Resource
    private RedisTemplate<String, String> redisTemplate;

    @Resource
    private HttpServletRequest request;

    @Before("@annotation(annotation)")
    public void resubmitCheck(JoinPoint joinPoint, RepeatSubmitCheck annotation) throws Throwable {

        final Object[] args = joinPoint.getArgs();
        final String[] conditionExpressions = annotation.conditionExpressions();

        //根据条件判断是否需要进行防重复提交检查
        if (!ExpressionUtils.getConditionValue(args, conditionExpressions) || ArrayUtils.isEmpty(args)) {
            return;
        }
        doCheck(annotation, args, joinPoint);
    }

    /**
     * key的组成为: prefix::userInfo::sessionId::uri::method::(根据spring EL表达式对参数进行拼接)
     *
     * @param annotation 注解
     * @param args       方法入参
     */
    private void doCheck(@NonNull RepeatSubmitCheck annotation, Object[] args, JoinPoint joinPoint) throws Throwable {
        final String[] argExpressions = annotation.argExpressions();
        final String message = annotation.message();
        final boolean withUserInfoInKey = annotation.withUserInfoInKey();
        final boolean onlyInCurrentSession = annotation.onlyInCurrentSession();
        final boolean withMethodInfo = annotation.withMethodInfo();

        String uri = request.getRequestURI();

        StringBuilder stringBuilder = new StringBuilder(64);
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        //Object[] argsForKey = ExpressionUtils.getExpressionValue(args, argExpressions);
        Object[] argsForKey = ExpressionUtils.getExpressionValueAliasAble(args, method, argExpressions);

        if (argsForKey == null) {
            return;
        }

        for (Object obj : argsForKey) {
            stringBuilder.append(obj.toString());
        }


        String methodInfo = method.getDeclaringClass().getSimpleName() + "_" + method.getName();

        StringBuilder keyBuilder = new StringBuilder();
        //userInfo一般从token中获取,可以使用当前登录的用户id作为标识
        keyBuilder.append(RESUBMIT_CHECK_KEY_PREFIX)
                //userInfo一般从token中获取,可以使用当前登录的用户id作为标识
                .append(withUserInfoInKey ? "userId" + REDIS_SEPARATOR : "")
                .append(onlyInCurrentSession ? request.getSession().getId() + REDIS_SEPARATOR : "")
                .append(uri)
                .append(REDIS_SEPARATOR)
                .append(withMethodInfo ? methodInfo + REDIS_SEPARATOR : "")
                .append(stringBuilder.toString());
        if (redisTemplate.opsForValue().get(keyBuilder.toString()) != null) {
            throw new CustomException(StringUtils.isBlank(message) ? Constant.RESUBMIT_MSG : message);
        }
        //值为空
        redisTemplate.opsForValue().set(keyBuilder.toString(), "", annotation.interval(), annotation.timeUnit());

    }
}
